﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace RQX.Common.Web.Tools
{
    /// <summary>
	/// 适合简单类型的自动映射的高性能方案
	/// </summary>
	/// <typeparam name="TSource">待转换的数据类型</typeparam>
	/// <typeparam name="TTarget">转换后的数据类型</typeparam>
	public class DataMapper<TSource, TTarget> where TSource : new() where TTarget : new()
    {
        private static Func<TSource, TTarget> MapFunc { get; set; }

        private Func<TSource, TTarget> memberMapFunc { get; set; }

        private Dictionary<string, string> memberConverter = new Dictionary<string, string>();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source">待转换的数据</param>
        /// <returns></returns>
        public static TTarget Map(TSource source)
        {
            if (MapFunc == null)
                MapFunc = GetMap();
            return MapFunc(source);
        }

        private TTarget Map(TSource source, Dictionary<string, string> member)
        {
            if (memberMapFunc == null) memberMapFunc = GetMapByMember(member);
            return memberMapFunc(source);
        }

        private Func<TSource, TTarget> GetMapByMember(Dictionary<string, string> member)
        {
            var sourceType = typeof(TSource);
            var targetType = typeof(TTarget);

            //构造 p=>
            var parameterExpression = Expression.Parameter(sourceType, "p");

            //构造 p=>new TTarget{ Id=p.Id,Name=p.Name };
            var memberBindingList = new List<MemberBinding>();
            foreach (var sourceItem in sourceType.GetProperties())
            {
                PropertyInfo targetItem;
                if (member.ContainsKey(sourceItem.Name))
                {
                    targetItem = targetType.GetProperty(member[sourceItem.Name]);
                }
                else
                {
                    targetItem = targetType.GetProperty(sourceItem.Name);
                }
                if (targetItem == null || sourceItem.PropertyType != targetItem.PropertyType)
                    continue;

                var property = Expression.Property(parameterExpression, sourceItem);
                var memberBinding = Expression.Bind(targetItem, property);
                memberBindingList.Add(memberBinding);
            }
            var memberInitExpression = Expression.MemberInit(Expression.New(targetType), memberBindingList);

            var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, parameterExpression);

            //Console.WriteLine(lambda);
            return lambda.Compile();
        }

        private static Func<TSource, TTarget> GetMap()
        {
            var sourceType = typeof(TSource);
            var targetType = typeof(TTarget);

            //构造 p=>
            var parameterExpression = Expression.Parameter(sourceType, "p");

            //构造 p=>new TTarget{ Id=p.Id,Name=p.Name };
            var memberBindingList = new List<MemberBinding>();
            foreach (var sourceItem in sourceType.GetProperties())
            {
                var targetItem = targetType.GetProperty(sourceItem.Name);
                if (targetItem == null || sourceItem.PropertyType != targetItem.PropertyType)
                    continue;

                var property = Expression.Property(parameterExpression, sourceItem);
                var memberBinding = Expression.Bind(targetItem, property);
                memberBindingList.Add(memberBinding);
            }
            var memberInitExpression = Expression.MemberInit(Expression.New(targetType), memberBindingList);

            var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, parameterExpression);

            //Console.WriteLine(lambda);
            return lambda.Compile();
        }

        public DataMapper<TSource, TTarget> ForMember(Func<TSource, string> getSourcePropertyName, Func<TTarget, string> getTargetPropertyName)
        {
            var source = getSourcePropertyName(new TSource());
            var target = getTargetPropertyName(new TTarget());
            memberConverter.Add(source, target);
            return this;
        }
        public TTarget MemberMap(TSource source)
        {
            return Map(source, memberConverter);
        }
    }
}
